package com.spzx.common.rabbit.config;

import com.alibaba.fastjson2.JSON;
import com.spzx.common.rabbit.entity.GuiguCorrelationData;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.weaver.ast.Var;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 服务器启动时，执行rabbitTemplate初始化，设置确认函数和回退函数
 * ApplicationEvent      一些子事件的父类。
 * ApplicationReadyEvent 具体子事件。表示应用程序启动好，IOC容器初始化好，存在相关bean对象了。再进行相关的初始化。
 * 也可以使用相关的注解替代： @EventListener
 */
@Slf4j
@Component
public class RabbitInitConfigApplicationListener implements ApplicationListener<ApplicationReadyEvent> {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    RedisTemplate redisTemplate;


    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        this.setUpInitRabbitTemplete();
    }


        /**
     * 初始化RabbitTemplate的回调配置
     *
     * 该方法用于设置RabbitTemplate的消息确认回调和返回回调，以确保消息能够正确发送到RabbitMQ。
     * 包含两个回调处理：
     * 1. ConfirmCallback：处理消息发送到Exchange的确认回调
     * 2. ReturnsCallback：处理消息从Exchange返回的情况（无法路由到队列时）
     */
    private void setUpInitRabbitTemplete() {

        // 设置消息发送到Exchange的确认回调
        // 当消息发送到Exchange后，无论成功或失败都会触发此回调
        // 参数说明：
        // - correlationData: 消息关联数据，包含消息ID等信息
        // - ack: 确认标志，true表示成功发送到Exchange，false表示发送失败
        // - cause: 失败原因，当ack为false时提供具体的失败原因
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            // 判断消息是否成功发送到Exchange
            if (ack) {
                // 消息发送成功，记录成功日志
                // correlationData包含消息的唯一标识和相关信息
                log.info("消息发送到Exchange成功：{}", correlationData);
            } else {
                // 消息发送失败，记录错误日志并输出失败原因
                log.error("消息发送到Exchange失败：{}", cause);
                // 调用重试机制，尝试重新发送消息
                retrySendMsg(correlationData);
            }
        });


        // 设置消息返回回调
        // 当消息无法路由到任何队列时（如Exchange不存在或RoutingKey不匹配），会触发此回调
        // 这种情况发生在Exchange收到了消息但没有队列与之匹配时
        rabbitTemplate.setReturnsCallback(returnedMessage -> {
            // 记录返回的消息信息，用于调试和监控
            log.info("returnedMessage=" + returnedMessage);

            // 从返回消息的属性中获取关联的UUID
            // spring_returned_message_correlation是Spring AMQP自动添加的头部信息
            // 用于关联原始发送的消息
            String uuid = returnedMessage.getMessage()
                    .getMessageProperties().getHeader("spring_returned_message_correlation");

            // 从Redis中获取原始的消息关联数据
            // 这里存储的是之前发送消息时保存的GuiguCorrelationData对象
            GuiguCorrelationData correlationData =(GuiguCorrelationData) redisTemplate.opsForValue().get(uuid);

            if(correlationData != null&& !correlationData.isDelay()){
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                retrySendMsg(correlationData);
            }
        });
    }


    /**
     * 重试发送消息方法
     * 实现消息发送失败后的重试机制，确保消息的可靠投递
     * 当消息发送到RabbitMQ失败时，自动进行重试，最多重试3次
     *
     * @param correlationData 消息关联数据，包含消息发送的相关信息和重试次数
     */
    private void retrySendMsg(CorrelationData correlationData) {
        // 将父类CorrelationData转换为自定义的GuiguCorrelationData
        // GuiguCorrelationData扩展了重试次数等属性
        GuiguCorrelationData guiguCorrelationData = (GuiguCorrelationData) correlationData;

        // 获取当前消息的重试次数
        int retryCount = guiguCorrelationData.getRetryCount();

        // 判断重试次数是否小于最大重试次数(3次)
        if (retryCount < 3) {
            // 增加重试次数
            guiguCorrelationData.setRetryCount(retryCount + 1);

            // 将更新后的关联数据重新存储到Redis中，设置10分钟过期时间
            // 保证重试次数的持久化，防止服务重启后重试次数丢失
            redisTemplate.opsForValue().set(correlationData.getId(), guiguCorrelationData, 10, TimeUnit.MINUTES);

            // 使用RabbitTemplate重新发送消息到RabbitMQ
            // 参数依次为：交换机名称、路由键、消息内容、关联数据(用于确认机制)
            rabbitTemplate.convertAndSend(guiguCorrelationData.getExchange(),
                    guiguCorrelationData.getRoutingKey(),
                    guiguCorrelationData.getMessage(),
                    correlationData);

            // 记录重试日志，便于监控和排查问题
            log.info("消息发送失败，准备重试第{}次，消息ID：{}", retryCount + 1, correlationData.getId());
            return; // 重试完成后直接返回，不再执行后续代码
        }

        // 如果重试次数已达到最大限制(3次)，则记录错误日志
        // 表示消息发送彻底失败，需要人工介入处理
        log.error("生产者超过最大重试次数，将失败的消息存入数据库用人工处理；给管理员发送邮件；给管理员发送短信；");
    }

}
